'use strict'

//切换到程序当前文件所在目录
process.chdir(__dirname)

const Titbit = require('titbit')
const TitbitToken = require('titbit-token')
const getOpenID = require('./lib/getOpenID')
const {cors, tofile, resource} = require('titbit-toolkit')
const config = require('./config/config.js')

const fs = require('fs')

const fsp = fs.promises

//上传图片的目录
let imagePath = './images'

const app = new Titbit({
  //启用调试模式
  debug: true,

  loadInfoFile: '--mem',
  //默认捕获开发者没有明确处理的异常
  //strong: true
})

//初始化token加解密扩展
let tok = new TitbitToken({
  //3小时有效
  expires: 10800_000,
  //用于加解密的密钥(密码)
  key: '1234567890qazwer',
})

//用于管理员token验证
let atok = new TitbitToken({
  //3小时有效
  expires: 10800_000,
  //用于加解密的密钥(密码)
  key: '12a4567890qazweo',
})

//tofile扩展会给上传的文件对象加上toFile方法
app.pre(new cors(config.cors)).use(new tofile)

//token值权限验证中间件
let loginVerify = async (ctx, next) => {
  //检测是否提交了token
  if (!ctx.headers.authorization) {
    //401状态码表示的是没有登录或者token值已经超时
    return ctx.status(401).send('未登录,请登录验证')
  }

  let vk = tok.verifyAccessToken(ctx.headers.authorization)

  if (!vk.ok) {
    //非法的token值，code表示错误的状态码，是一个字符串。
    return ctx.status(401).send(vk.code)
  }

  //验证通过，解密后的数据在data里面
  //把data赋值给ctx.box.user，后续函数可以获取
  ctx.box.user = vk.data

  await next()
}

let adminTokenVerify = async (c, next) => {
  let token = c.headers.authorization

  if (!token) {
    return c.status(401).send('!TOKEN')
  }

  let uinfo = atok.verifyAccessToken(token)

  if (!uinfo.ok) {
    return c.status(401).send(uinfo.errcode)
  }

  c.box.user = uinfo.data

  await next()
}

if (app.isWorker) {
  /**
   * 处理小程序登录：
   *   - 使用appid和secret + code获取用户的openid
   *   - 成功之后，对信息进行加密，返回加密的数据即为token值（登录凭证）
   */
  app.get('/mp-login/:code', async ctx => {
    //路由参数 code 放在ctx.param中
    let tk = await getOpenID(ctx.param.code)
    if (!tk.ok) {
      return ctx.status(400).send('请求遇到网络错误')
    }
    //把返回值转成object
    let info = tk.json()
    
    //获取失败，返回400状态码，并返回错误信息
    if (!info.openid || info.errcode) {
      return ctx.status(400).send(info.errmsg)
    }

    //return ctx.send(info)
    //加密处理返回token值
    let data = {
      //每次登录都有一个随机值，保证生成的token不一致
      rand: Math.random(),
      openid: info.openid,
    }

    ctx.send({
      token: tok.makeAccessToken(data),
      expires: tok.expires
    })

  }, {name: '登录', group: 'login'})
}

//加载路由文件
function loadRoutes(path) {

  try {
    fs.accessSync(path)
    let fst = fs.stat(path)
    if (!fst.isDirectory()) return {}
  } catch (err) {
    return {}
  }

  let flist = fs.readdirSync(path, {withFileTypes: true})
  let rlist = []
  for (let f of flist) {
    if (f.isFile() && f.name.length > 3 && f.name.substring(f.name.length - 3) === '.js') {
      rlist.push(f.name)
    }
  }

  let obj = {}

  for (let r of rlist) {
    try {
      obj[f.name.substring(0, f.name.length - 3)] = require(`${path}/${f.name}`)
    } catch (err) {
      console.error(err)
    }
  }

  return obj
}

if (app.isWorker) {
  app.group('/api', route => {
      //添加上传图片路由，post请求，路径是 /api/image
      route.post('/image', async ctx => {
        //获取文件对象，上传名是image
        let imgfile = ctx.getFile('image')
        if (!imgfile) {
          return ctx.status(400).send('没有图片文件')
        }

        let upload_name = await imgfile.toFile(imagePath)

        ctx.send({upload_name})
      })      

  }).middleware([loginVerify], {pre: true})
  //middleware是group的返回值具备的方法，设置要启用的中间件，pre表示在接收body数据之前执行

  //此处定义admin路由，可以使用loadRoutes加载目录下的路由模块
  app.group('/admin', route => {

  })
  .middleware([adminTokenVerify], {pre: true})
  
  
  // 返回图片数据，前端使用img标签的src属性引入图片，示例：src="/image/a.jpg"
  app.get('/image/:name', async ctx => {
    try {
      //路由参数都放在ctx.param中
      let imgfile = `${imagePath}/${ctx.param.name}`
      //ctx.reply是响应对象，在HTTP/1.1是response实例
      await ctx.helper.pipe(imgfile, ctx.reply)
    } catch (err) {
      return ctx.status(404).send('image not found')
    }
  }, {name: 'get-image'}) //给一个路由请求命名

}

config.autoWorker > 1 && app.autoWorker(config.autoWorker)

app.daemon(config.port, config.worker)
